package in.model.base;
import in.lib.Debug;
import in.lib.Regex;
import in.lib.manager.CacheManager;
import in.lib.manager.SettingsManager;
import in.lib.manager.UserManager;
import in.lib.utils.URLUtils;
import in.model.User;
import in.obj.annotation.Annotation;
import in.obj.annotation.Annotation.Type;
import in.obj.annotation.CrosspostAnnotation;
import in.obj.annotation.ImageAnnotation;
import in.obj.annotation.LinkAnnotation;
import in.obj.annotation.LocationAnnotation;
import in.obj.annotation.RichAnnotation;
import in.obj.annotation.VideoAnnotation;
import in.obj.entity.LinkEntity;
import in.obj.entity.MentionEntity;
import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.Calendar;
import java.util.Date;
import java.util.GregorianCalendar;
import java.util.HashMap;
import java.util.LinkedHashMap;
import java.util.TimeZone;
import java.util.regex.Matcher;
import lombok.Getter;
import lombok.Setter;
import lombok.ToString;
import android.net.Uri;
import android.os.Parcel;
import android.os.Parcelable;
import com.esotericsoftware.kryo.serializers.TaggedFieldSerializer.Tag;
import com.google.gson.JsonArray;
import com.google.gson.JsonElement;
import com.google.gson.JsonObject;
@ToString(includeFieldNames = true, callSuper = true)
public class Message extends NetObject
{
@Tag(0x01) @Getter @Setter private String formattedText;
@Tag(0x10) @Getter @Setter private String originalText;
@Tag(0x02) @Getter @Setter private User poster;
@Tag(0x03) @Getter @Setter private boolean mention;
@Tag(0x04) @Getter @Setter private String canonicalUrl;
@Tag(0x05) @Getter @Setter private String timeZone;
@Tag(0x06) @Getter @Setter private String clientName;
@Tag(0x07) @Getter @Setter private String clientLink;
@Tag(0x08) @Getter @Setter private String replyTo;
@Tag(0x09) @Getter @Setter private long date;
@Tag(0x0A) @Getter @Setter private String dateStr;
@Tag(0x0B) @Getter @Setter private HashMap<Type, ArrayList<Annotation>> annotations;
@Tag(0x0C) @Getter @Setter private String[] hashTags = {};
@Tag(0x0D) @Getter @Setter private LinkEntity[] links = {};
@Tag(0x0E) @Getter @Setter private MentionEntity[] mentions = {};
@Tag(0x0F) @Getter @Setter private boolean machinePost;
/**
* Set this to true if it is a faux post added to the timeline. Check
* for this when finding the first post in the list's ID to prevent
* timeline shifting
*/
@Tag(0x10) @Getter @Setter private boolean newPost = false;
@Override public Message createFrom(JsonObject post)
{
return createFrom(post, UserManager.getUser(), false);
}
public Message createFrom(JsonObject post, boolean allowDeleted)
{
return createFrom(post, UserManager.getUser(), allowDeleted);
}
/**
* Parses the return API object into a post class using the specified user
* @param p The message object to populate
* @param user The jsonobject post from the API
* @param acc The user to use when parsing the object
* @return The new Post object
*/
public Message createFrom(JsonObject post, User acc, boolean allowDeleted)
{
if (post.get("is_deleted") != null && post.get("is_deleted").getAsBoolean() && !allowDeleted)
{
return null;
}
try
{
SimpleDateFormat format = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss'Z'");
format.setTimeZone(TimeZone.getTimeZone("UTC"));
Date postDate = format.parse(post.get("created_at").getAsString());
long date = postDate.getTime();
setId(post.get("id").getAsString());
setDate(date);
setDateStr(calculateDateString());
if (post.has("html"))
{
String html = post.get("html").getAsString()
.replaceAll("\\s{2}", " ")
.replaceAll("\\t+", " ")
.replaceAll("(<br>(\\s)?){3,}", "<br />")
.replaceAll("(?:\\<br\\s*/?\\>)+\\</span\\>*$", "</span>");
if (SettingsManager.isBoldEnabled())
{
Matcher asterix = Regex.MATCH_ASTERIX.matcher(html);
while (asterix.find())
{
String group = asterix.group();
int start = asterix.start();
int end = asterix.end();
String begin = html.substring(0, start);
String finish = html.substring(end, html.length());
html = begin + "<b>" + group.substring(2, group.length() - 2) + "</b>" + finish;
asterix = Regex.MATCH_ASTERIX.matcher(html);
}
}
if (SettingsManager.isItalicEnabled())
{
Matcher slash = Regex.MATCH_SLASH.matcher(html);
while (slash.find())
{
String group = slash.group();
int start = slash.start();
int end = slash.end();
String begin = html.substring(0, start);
String finish = html.substring(end, html.length());
html = begin + "<i>" + group.substring(1, group.length() - 1) + "</i>" + finish;
slash = Regex.MATCH_SLASH.matcher(html);
}
}
if (SettingsManager.isUnderlineEnabled())
{
Matcher underscore = Regex.MATCH_UNDERSCORE.matcher(html);
while (underscore.find())
{
String group = underscore.group();
int start = underscore.start();
int end = underscore.end();
String begin = html.substring(0, start);
String finish = html.substring(end, html.length());
html = begin + "<u>" + group.substring(1, group.length() - 1) + "</u>" + finish;
underscore = Regex.MATCH_UNDERSCORE.matcher(html);
}
}
setFormattedText(html);
}
if (post.has("text"))
{
setOriginalText(post.get("text").getAsString());
}
setTimeZone(post.get("user").getAsJsonObject().get("timezone").getAsString());
setClientName(post.get("source").getAsJsonObject().get("name").getAsString());
setClientLink(post.get("source").getAsJsonObject().get("link").getAsString());
User poster = new User().createFrom(post.get("user").getAsJsonObject());
setPoster(poster);
if (post.has("reply_to"))
{
setReplyTo(post.get("reply_to").getAsString());
}
JsonArray mentions = post.get("entities").getAsJsonObject().get("mentions").getAsJsonArray();
if (mentions.size() > 0)
{
setMentions(parseMentions(mentions, acc));
if (mentions != null)
{
for (MentionEntity men : getMentions())
{
if (men.getId().equals(acc.getId()))
{
setMention(true);
break;
}
}
}
}
JsonArray links = post.get("entities").getAsJsonObject().get("links").getAsJsonArray();
if (links.size() > 0)
{
setLinks(parseLinks(links));
}
JsonArray hashTags = post.get("entities").getAsJsonObject().get("hashtags").getAsJsonArray();
if (hashTags.size() > 0)
{
setHashTags(parseHashTags(hashTags));
}
if (post.has("annotations"))
{
JsonArray annotations = post.get("annotations").getAsJsonArray();
if (annotations.size() > 0 || this.links.length > 0)
{
setAnnotations(new LinkedHashMap<Annotation.Type, ArrayList<Annotation>>());
setAnnotations(parseAnnotations(annotations, links));
}
}
return this;
}
catch (Exception e)
{
Debug.out(e);
return null;
}
}
@Override public Message createFrom(Parcel object)
{
setFormattedText(object.readString());
setOriginalText(object.readString());
setPoster((User)object.readParcelable(User.class.getClassLoader()));
setMention((Boolean)object.readValue(null));
setCanonicalUrl(object.readString());
setTimeZone(object.readString());
setClientName(object.readString());
setClientLink(object.readString());
setReplyTo(object.readString());
setDate(object.readLong());
setDateStr(object.readString());
setAnnotations((HashMap<Annotation.Type, ArrayList<Annotation>>)object.readSerializable());
String[] tags = new String[object.readInt()];
object.readStringArray(tags);
setHashTags(tags);
setLinks((LinkEntity[])object.readSerializable());
setMentions((MentionEntity[])object.readSerializable());
setMachinePost((Boolean)object.readValue(null));
super.createFrom(object);
return this;
}
/**
* Goes through a json array of mentions and adds them to the list
* @param mentions The mentions from the API
* @return An ArrayList of mentions
*/
public MentionEntity[] parseMentions(JsonArray mentions, User acc)
{
MentionEntity[] mens = new MentionEntity[mentions.size()];
int index = 0;
for (JsonElement mention : mentions)
{
mens[index++] = new MentionEntity(mention.getAsJsonObject().get("id").getAsString(), mention.getAsJsonObject().get("name").getAsString());
}
return mens;
}
/**
* Processes the links entity object into class struct list
* @param links
* @return
*/
public LinkEntity[] parseLinks(JsonArray links)
{
LinkEntity[] linksArr = new LinkEntity[links.size()];
int index = 0;
for (JsonElement e : links)
{
LinkEntity link = new LinkEntity();
link.setLen(e.getAsJsonObject().get("len").getAsInt());
link.setPos(e.getAsJsonObject().get("pos").getAsInt());
link.setUrl(e.getAsJsonObject().get("url").getAsString());
if (e.getAsJsonObject().has("amended_len"))
{
link.setAmendedLen(e.getAsJsonObject().get("amended_len").getAsInt());
}
linksArr[index++] = link;
}
return linksArr;
}
/**
* Goes through a json array of tags and adds them to the list
* @param hashTags The tags from the API
* @return An ArrayList of tags
*/
public String[] parseHashTags(JsonArray hashTags)
{
String[] tags = new String[hashTags.size()];
int index = 0;
for (JsonElement tag : hashTags)
{
tags[index++] = tag.getAsJsonObject().get("name").getAsString().toLowerCase();
}
return tags;
}
/**
* Goes through annotations and embedded links in a post. Adds them to the relevant type in the HashMap
* Any duplicate images found encoded in the post and annotation will be skipped.
*
* @param annotations The annotation jsonArray
* @param jLinks The links jsonArray
* @return The finished hashmap
*/
public HashMap<Type, ArrayList<Annotation>> parseAnnotations(JsonArray annotations, JsonArray jLinks)
{
HashMap<Type, ArrayList<Annotation>> ann = new HashMap<Type, ArrayList<Annotation>>();
ArrayList<Annotation> locs = new ArrayList<Annotation>();
ArrayList<Annotation> images = new ArrayList<Annotation>();
ArrayList<Annotation> videos = new ArrayList<Annotation>();
ArrayList<Annotation> rich = new ArrayList<Annotation>();
ArrayList<Annotation> links = new ArrayList<Annotation>();
ArrayList<Annotation> crossPost = new ArrayList<Annotation>();
ArrayList<Annotation> inOrder = new ArrayList<Annotation>();
ArrayList<String> tmpImageList = new ArrayList<String>();
for (JsonElement annotation : annotations)
{
JsonObject a = annotation.getAsJsonObject();
JsonObject value = a.get("value").getAsJsonObject();
String type = a.get("type").getAsString();
if (type.equals("net.app.core.geolocation"))
{
LocationAnnotation location = new LocationAnnotation();
location.setLat(value.get("latitude").getAsDouble());
location.setLng(value.get("longitude").getAsDouble());
locs.add(location);
}
else if (type.equals("net.app.core.crosspost"))
{
if (value.has("canonical_url"))
{
CrosspostAnnotation cross = new CrosspostAnnotation();
cross.setUrl(value.get("canonical_url").getAsString());
crossPost.add(cross);
}
}
else if (type.equals("net.app.core.oembed"))
{
if (value.has("type"))
{
if (value.get("type").getAsString().equalsIgnoreCase("video"))
{
VideoAnnotation image = new VideoAnnotation();
if (!value.has("thumbnail_url")) continue;
image.setPreviewUrl(value.get("thumbnail_url").getAsString());
image.setUrl(value.get("thumbnail_url").getAsString());
image.setTextUrl(value.get("thumbnail_url").getAsString());
image.setWidth(value.get("width").getAsInt());
image.setHeight(value.get("height").getAsInt());
if (value.has("embeddable_url"))
{
image.setEmbeddableUrl(value.get("embeddable_url").getAsString());
image.setUrl(value.get("embeddable_url").getAsString());
image.setTextUrl(value.get("embeddable_url").getAsString());
tmpImageList.add(image.getEmbeddableUrl());
}
else
{
tmpImageList.add(image.getUrl());
}
videos.add(image);
inOrder.add(image);
}
else if (value.get("type").getAsString().equalsIgnoreCase("photo"))
{
ImageAnnotation image = new ImageAnnotation();
image.setUrl(value.get("url").getAsString());
image.setTextUrl(value.get("url").getAsString());
image.setWidth(value.get("width").getAsInt());
image.setHeight(value.get("height").getAsInt());
if (value.has("thumbnail_large_url_immediate"))
{
image.setThumbUrl(value.get("thumbnail_large_url_immediate").getAsString());
image.setPreviewUrl(image.getThumbUrl());
}
else if (value.has("thumbnail_url"))
{
image.setThumbUrl(value.get("thumbnail_url").getAsString());
image.setPreviewUrl(image.getThumbUrl());
}
if (value.has("embeddable_url"))
{
image.setEmbeddableUrl(value.get("embeddable_url").getAsString());
}
tmpImageList.add(image.getUrl());
images.add(image);
inOrder.add(image);
}
else if (value.get("type").getAsString().equalsIgnoreCase("rich"))
{
RichAnnotation richAnnotation = new RichAnnotation();
richAnnotation.setUrl(value.get("url").getAsString());
if (value.has("thumbnail_large_url_immediate"))
{
richAnnotation.setThumbUrl(value.get("thumbnail_large_url_immediate").getAsString());
richAnnotation.setPreviewUrl(richAnnotation.getThumbUrl());
}
else if (value.has("thumbnail_url"))
{
richAnnotation.setThumbUrl(value.get("thumbnail_url").getAsString());
richAnnotation.setPreviewUrl(richAnnotation.getThumbUrl());
}
if (value.has("embeddable_url"))
{
richAnnotation.setUrl(value.get("embeddable_url").getAsString());
richAnnotation.setEmbeddableUrl(value.get("embeddable_url").getAsString());
}
tmpImageList.add(richAnnotation.getUrl());
tmpImageList.add(richAnnotation.getEmbeddableUrl());
rich.add(richAnnotation);
inOrder.add(richAnnotation);
}
}
}
}
for (JsonElement link : jLinks)
{
JsonObject l = link.getAsJsonObject();
String stringUrl = l.get("url").getAsString();
Uri uri = Uri.parse(stringUrl);
if (URLUtils.isImage(uri) && !tmpImageList.contains(stringUrl))
{
ImageAnnotation image = new ImageAnnotation();
image.setUrl(stringUrl);
image.setTextUrl(stringUrl);
image.setEmbeddableUrl(stringUrl);
tmpImageList.add(stringUrl);
images.add(image);
inOrder.add(image);
}
else if (URLUtils.isYoutubeVideo(uri))
{
VideoAnnotation image = new VideoAnnotation();
image.setUrl(stringUrl);
image.setTextUrl(stringUrl);
videos.add(image);
inOrder.add(image);
}
// else if (URLUtils.isMap(uri))
// {
// LocationAnnotation location = new LocationAnnotation();
//
// locs.add(new OldAnnotation(stringUrl, Type.LOCATION));
// }
else
{
LinkAnnotation linkAnnotation = new LinkAnnotation();
linkAnnotation.setUrl(stringUrl);
links.add(linkAnnotation);
}
}
ann.put(Type.IMAGE, images);
ann.put(Type.VIDEO, videos);
ann.put(Type.RICH, rich);
ann.put(Type.LOCATION, locs);
ann.put(Type.LINK, links);
ann.put(Type.CROSS_POST, crossPost);
ann.put(Type.IN_ORDER, inOrder);
return ann;
}
public String calculateDateString()
{
if (SettingsManager.getShowLongDates())
{
GregorianCalendar cal = new GregorianCalendar();
GregorianCalendar todayDate = new GregorianCalendar(cal.get(Calendar.YEAR), cal.get(Calendar.MONTH), cal.get(Calendar.DATE));
long startDate = todayDate.getTimeInMillis();
String time = "";
if (getDate() > startDate)
{
time = SettingsManager.getTimeFormat().format(new Date(getDate()));
}
else
{
time = SettingsManager.getDateFormat().format(new Date(getDate()));
}
return time;
}
else
{
return SettingsManager.getDateUtils().timeAgo(getDate());
}
}
/**
* Checks if the post has a hash tag which has been muted
* @return True if it is muted, false otherwise
*/
public boolean isMuted()
{
if (this.hashTags == null) return false;
for (String tag : this.hashTags)
{
if (SettingsManager.isTagMuted(tag))
{
return true;
}
}
return false;
}
@Override public void writeToParcel(Parcel dest, int flags)
{
dest.writeString(getFormattedText());
dest.writeString(getOriginalText());
dest.writeParcelable(getPoster(), 0);
dest.writeValue(isMention());
dest.writeString(getCanonicalUrl());
dest.writeString(getTimeZone());
dest.writeString(getClientName());
dest.writeString(getClientLink());
dest.writeString(getReplyTo());
dest.writeLong(getDate());
dest.writeString(getDateStr());
dest.writeSerializable(getAnnotations());
dest.writeInt(getHashTags().length);
dest.writeStringArray(getHashTags());
dest.writeSerializable(getLinks());
dest.writeSerializable(getMentions());
dest.writeValue(isMachinePost());
super.writeToParcel(dest, flags);
}
public static final Parcelable.Creator<Message> CREATOR = new Creator<Message>()
{
@Override public Message[] newArray(int size)
{
return new Message[size];
}
@Override public Message createFromParcel(Parcel source)
{
return new Message().createFrom(source);
}
};
public static Message deserialize(byte[] data)
{
return CacheManager.getInstance().deserialize(data, Message.class);
}
}